8 数组

变量:变量分为两种

分类 说明
标量(scalar) 标亮具有保存单一数据项的能力
聚合(aggregate) 存储数值的集合(数组(array)和结构(structure))

8.1 一维数组

说明:数组是含有多个数据值的数据结构,并且每个数据之具有相同的数据类型。这些数据类型的值被称为元素(element)。
声明数组:需要说明数组元素的类型(任何类型)和数量(任何整数常量表达式)。

1
int a[10];

技巧:因为程序后面变化时可能需要调整数组的长度,所以较好的方法是用宏来定义数组的长度。

1
2
#define N 10
int a[N];

8.1.1 数组下标

说明:为了存取特定的数组元素,可以在写数组名的同时在后迷呐加上一个用方括号围绕的整数值(称这是对数组进行下标(subsvripting)或索引(indexing))。
注意:

  • a[i]的表达式格式是左值,所以数组元素可以和普通变量一样使用
  • 在调用scanf函数读取数组元素时,就像对待普通变量一样,必须使用取地址符号&
  • c语言不要求检查下标的范围,当下标超出范围时,程序可能执行不可预知的行为。
1
2
3
4
5
6
int a[10], i;
//对某些编译器来说,这个表面上正确的for语句却产生了一个无限循环
//如果i在内存中的位置在a[9]后面,那么修改a[10]其实是修改了i,使i变为0
for(i = 0; i < 10; i++){
a[i] = 0;
}

技巧:避免数组下标的副作用。

1
2
3
4
5
6
7
8
//不确定的做法
while(i < N){
a[i] = b[i++];//i有可能会在复制发生之前就进行自增,导致两个数组错位
}
//安全的做法
for(i = 0; i < N; i++){
a[i] = b[i];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a[0] = 1;
printf("%d\n", a[5]);
++a[i];

//清空数组
for(i = 0; i < N; i++){
a[i] = 0;
}

//读取数组元素
for(i = 0; i < N; i++){
scanf("%d", &a[i]);
}

//求和
for(i = 0; i < N; i++){
sun += a[i];
}

8.1.2 程序:数列反向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Reverse a series of numbers
*/

#include <stdio.h>
#define N 10
int main(){
int a[N], i;

printf("Enter %d numbers:", N);
for (i = 0; i < N; ++i){
scanf("%d", &a[i]);
}
printf("In reverse order:");
for(i = N-1; i >= 0; --i){

printf(" %d", a[i]);
}
printf("\n");

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
$ ./reverse
Enter 10 numbers:10
9
8
7
6
5
4
3
2
1
In reverse order: 1 2 3 4 5 6 7 8 9 10

8.1.3 数组初始化

常量表达式列表规则:

  • 列表用{}括起来,内部数值用,分隔
  • 初始化式完全为空或比数组长度长是非法的,但可以比数组短,此时数组中生育的元素赋值为0
1
2
3
4
5
6
7
8
9
10
11
//完整的格式
int a[10] = {1,2,3,4,5,6,7,8,9,10};

//省略数组的长度,此时编译器会利用初始化式的长度确定数组的大小
int a[] = {1,2,3,4,5,6,7,8,9,10};

//初始化式比数组长度短
int a[10] = {1,2,3,4,5,6};//{1,2,3,4,5,6,0,0,0,0};

//全部数组元素初始化为0
int a[10] = {0};//{0,0,0,0,0,0,0,0,0,0}

8.1.4 程序:检查数中重复出现的数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* Checks numbers for repeated digits
*/

#include <stdio.h>
#define TRUE 1
#define FALSE 0

typedef int Bool;

int main(){
Bool digit_seen[10] = {0};
int digit;
long int n;

printf("Enter a number:");
scanf("%ld", &n);

while(n > 0){
//取个位数
digit = n % 10;
//检查个位数是不是已经有过一个了,是的话终止循环
if(digit_seen[digit]){
break;
}
//以当前个位数作为索引将对应数字元素的值改为TRUE
digit_seen[digit] = TRUE;
n /= 10;
}
if(n>0){
//n>0说明至少有一个数字发生了重复
printf("Repeated digit\n\n");
}else{
printf("No repeated digit\n\n");
}
return 0;
}
1
2
3
./repdigit 
Enter a number:12345677
Repeated digit

8.1.5 对数组使用sizeof运算符

用途:

  • 计算数组的字节数
  • 计算数组中每个元素的字节数
  • 利用上面的结果计算数组的长度
1
2
3
for(i = 0; i < sizeof(a) / sizeof(a[10]); i++){
a[i] = 0;
}

利用宏:简化用sizeof计算数组长度。更加有效的方式是使用带参数的宏。

1
2
3
4
#define SIZE (sizeof(a) / sizeof(a[0]))
for(i = 0; i < SIZE; i++){
a[i] = 0;
}

8.1.6 程序:计算利息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Prints a table of compound interest
*/

#include <stdio.h>
#define NUM_RATES (sizeof(value) / sizeof(value[0]))
#define INITIAL_BALANCE 100.00

int main(){
int i, low_rate, num_years, year;
float value[5];

printf("Enter interest rate:");
scanf("%d", &low_rate);
printf("Enter number of years:");
scanf("%d", &num_years);

//表格标题行
printf("\nYears");
for(i = 0; i < NUM_RATES; i++){
printf("%6d%%", low_rate + i);//两个%%代表要显示%字符串
value[i] = INITIAL_BALANCE;
}
printf("\n");
//各个利率下逐年100美金的价值
for(year = 1; year <= num_years; year++){
printf("%3d ", year);
for(i = 0; i < NUM_RATES; i++){
value[i] += (low_rate+i) / 100.0 * value[i];
printf("%7.2f", value[i]);
}
printf("\n");
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
$ ./interest                            
Enter interest rate:5
Enter number of years:6

Years 5% 6% 7% 8% 9%
1 105.00 106.00 107.00 108.00 109.00
2 110.25 112.36 114.49 116.64 118.81
3 115.76 119.10 122.50 125.97 129.50
4 121.55 126.25 131.08 136.05 141.16
5 127.63 133.82 140.26 146.93 153.86
6 134.01 141.85 150.07 158.69 167.71

8.2 多维数组

语法:m[i][j]
注意:不能把m[i][j]写为m[i, j]。c语言把逗号看成是逗号运算符,所以m[i,j]等同于m[j]
地位:和其它编程语言中的多位数组相比, c语言中的多位数组扮演的角色相对较弱,这主要是因为c语言为存储多维数组提供了更加灵活的方法:指针数组。

1
2
3
4
5
6
7
8
9
10
11
12
#define N 10
float ident[N][N];
int row, col;
for(row = 0; row < N; row++){
for(col = 0; col < N; col++){
if(row == col){
ident[row][col] = 1.0;
}else{
ident[row][col] = 0.0;
}
}
}

8.2.1 多为数组的初始化

说明:通过嵌套一维初始化式的方式可以产生二维数组的初始化式。
规则:c 语言提供了多种方法来缩写初始化式。

  • 如果初始化式不大到足以填满整个多位数组,那么数组中剩余的元素会被赋值为0。
  • 如果内层的列表不大到足以填满数组的一行,那么慈航的剩余元素会被初始化为0。
  • 可以忽略掉内层的大括号
    注意:在多维数组中忽略掉内层的大括号可能很危险,此外省略括号会引起某些编译器产生警告信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//行数不足
int m[5][9] = {{1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1},
{1,1,1,1,1,1,1,1,1}};
//列数不足
int m[5][9] = {{1,1,1,1,1,1},
{1,1,1,1,1,1},
{1,1,1,1,1,1},
{1,1,1,1,1,1},
{1,1,1,1,1,1}};
//忽略内层的大括号
int m[5][9] = {1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1,
1,1,1,1,1,1,1,1,1};

8.2.2 常量数组

语法:const 类型 数组名[]
说明:程序不应对声明为const的数组进行修改。

  • 表明程序不会修改数组
  • 告知不打算修改数组对编译器发现错误是很有帮助的

8.2.3 发牌

相关函数:

函数 所属库 说明
time time.h 返回当前的时间,且这个时间是被编码成单独的数。
srand stdlib.h 初始化c语言的随机数生成器
rand stdlib.h 在每次调用时会产生一个显然随机的数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* Deals a random hand of cards
*/

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define NUM_SUITS 4
#define NUM_RANKS 13
#define TRUE 1
#define FALSE 0

typedef int Bool;

int main(){
Bool in_hand[NUM_SUITS][NUM_RANKS] = {{0}};
int num_cards, rank, suit;
const char rank_code[] = {'2','3','4','5','6','7','8','9','t','j','q','k','a'};
const char suit_code[] = {'c', 'd', 's', 'h'};
srand((unsigned)time(NULL));
printf("Enter number of cards in hand:");
scanf("%d", &num_cards);

printf("Your hand:");
while(num_cards > 0){
suit = rand() % NUM_SUITS;//检出随机的花面
rank = rand() % NUM_RANKS;//检出随机的纸牌等级
//如果某张牌已经发出去过,则不能再发同样的牌,应当重新发
if(!in_hand[suit][rank] == TRUE){
in_hand[suit][rank] = TRUE;
num_cards--;
printf(" %c%c", rank_code[rank], suit_code[suit]);
}
}
printf("\n");
return 0;

}
1
2
3
$ ./deal                        
Enter number of cards in hand:10
Your hand: 5s 6d th 9c 6s qs 7c 9d kh 2d